package com.google.code.jgchart;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;

public class Axis implements UrlQuery {

	private static final String AXIS_TYPE_PARAM = "chxt=";

	public static final String BOTTOM_X_AXIS = "x";

	public static final String TOP_X_AXIS = "t";

	public static final String LEFT_Y_AXIS = "y";

	public static final String RIGHT_Y_AXIS = "r";

	private static final String AXIS_LABEL_PARAM = "chxl=";

	private static final String AXIS_LABEL_POSITION_PARAM = "chxp=";

	private static final String AXIS_RANGE_PARAM = "chxr=";

	private static final String AXIS_STYLE_PARAM = "chxs=";

	private String urlCodec = "UTF-8";

	private List<String> axisTypes = new ArrayList<String>();

	private Map<Integer, List<String>> axisLabelsMap = new TreeMap<Integer, List<String>>();

	private Map<Integer, List<Integer>> axisLabelPositionsMap = new TreeMap<Integer, List<Integer>>();

	private Map<Integer, int[]> axisRangeMap = new TreeMap<Integer, int[]>();

	private Map<Integer, AxisStyle> axisStyleMap = new TreeMap<Integer, AxisStyle>();

	public Axis() {
	}

	public Axis setUrlCodec(String urlCodec) {
		this.urlCodec = urlCodec;
		return this;
	}

	public Axis appendAxisType(String... types) {
		for (int i = 0; i < types.length; i++) {
			String type = types[i];
			this.axisTypes.add(type);
			// if (type.equals(BOTTOM_X_AXIS) || type.equals(TOP_X_AXIS)
			// || type.equals(LEFT_Y_AXIS) || type.equals(RIGHT_Y_AXIS))
			// this.axisTypes.add(type);
			// else
			// throw new IllegalArgumentException("invalid axis type.");
		}
		return this;
	}

	private Axis appendAxisType(int time, String type) {
		for (int i = 0; i < time; i++)
			axisTypes.add(type);
		return this;
	}

	public Axis appendBottomXAxis(int times) {
		return appendAxisType(times, BOTTOM_X_AXIS);
	}

	public Axis appendTopXAxis(int times) {
		return appendAxisType(times, TOP_X_AXIS);
	}

	public Axis appendLeftYAxis(int times) {
		return appendAxisType(times, LEFT_Y_AXIS);
	}

	public Axis appendRightYAxis(int times) {
		return appendAxisType(times, RIGHT_Y_AXIS);
	}

	public Axis appendLabels(int index, String... lables) {
		List<String> labelList = axisLabelsMap.get(index);
		if (labelList == null) {
			labelList = new ArrayList<String>();
			axisLabelsMap.put(index, labelList);
		}
		for (int i = 0; i < lables.length; i++) {
			labelList.add(lables[i]);
		}
		return this;
	}

	public Axis appendLabelPositions(int index, int... positions) {
		List<Integer> positionsList = axisLabelPositionsMap.get(index);
		if (positionsList == null) {
			positionsList = new ArrayList<Integer>();
			axisLabelPositionsMap.put(index, positionsList);
		}
		for (int i = 0; i < positions.length; i++) {
			positionsList.add(positions[i]);
		}
		return this;
	}

	public Axis appendLabelPositions(int[] index, int... positions) {
		for (int i : index) {
			appendLabelPositions(i, positions);
		}
		return this;
	}

	public Axis setRange(int index, int start, int end) {
		int[] range = axisRangeMap.get(index);
		if (range == null) {
			range = new int[2];
			axisRangeMap.put(index, range);
		}
		range[0] = start;
		range[1] = end;
		return this;
	}

	public Axis setStyle(int index, AxisStyle style) {
		if (style == null) {
			axisStyleMap.remove(index);
		} else {
			axisStyleMap.put(index, style);
		}
		return this;
	}

	public Axis setStyle(int[] index, AxisStyle style) {
		for (int i : index) {
			setStyle(i, style);
		}
		return this;
	}

	private void bindAxisTypeQuery(List<String> queries) {
		if (axisTypes.size() > 0) {
			StringBuilder sb = new StringBuilder();
			sb.append(AXIS_TYPE_PARAM).append(axisTypes.get(0));
			for (int i = 1; i < axisTypes.size(); i++) {
				sb.append(",").append(axisTypes.get(i));
			}
			queries.add(sb.toString());
		}
	}

	private void bindAxisLabelsQuery(List<String> queries) {
		if (axisLabelsMap.size() > 0) {
			StringBuilder sb = new StringBuilder();
			URLCodec codec = new URLCodec(urlCodec);
			sb.append(AXIS_LABEL_PARAM);
			Iterator<Integer> indexIter = axisLabelsMap.keySet().iterator();

			while (indexIter.hasNext()) {
				Integer index = indexIter.next();
				sb.append(index).append(":");
				List<String> labels = axisLabelsMap.get(index);
				for (int i = 0; i < labels.size(); i++) {
					try {
						sb.append("|").append(codec.encode(labels.get(i)));
					} catch (EncoderException e) {
						throw genQueryEncoderException("label: " + labels.get(i), urlCodec,
								e);
					}
				}
				if (indexIter.hasNext())
					sb.append("|"); // index split
			} // end: while (indexIter.hasNext())
			queries.add(sb.toString());
		} // end: if (axisLabelsMap.size() > 0)
	}

	private void bindAxisLabelPositionsQuery(List<String> queries) {
		if (axisLabelPositionsMap.size() > 0) {
			StringBuilder sb = new StringBuilder();
			sb.append(AXIS_LABEL_POSITION_PARAM);
			Iterator<Integer> indexIter = axisLabelPositionsMap.keySet().iterator();
			while (indexIter.hasNext()) {
				Integer index = indexIter.next();
				sb.append(index);
				List<Integer> labelPositions = axisLabelPositionsMap.get(index);
				for (int i = 0; i < labelPositions.size(); i++) {
					sb.append(",").append(labelPositions.get(i));
				}
				if (indexIter.hasNext())
					sb.append("|"); // index split
			} // end: while (indexIter.hasNext())
			queries.add(sb.toString());
		} // end: if (axisLabelPositionsMap.size() > 0)
	}

	private void bindAxisRangeQuery(List<String> queries) {
		if (axisRangeMap.size() > 0) {
			StringBuilder sb = new StringBuilder();
			sb.append(AXIS_RANGE_PARAM);
			Iterator<Integer> indexIter = axisRangeMap.keySet().iterator();
			while (indexIter.hasNext()) {
				Integer index = indexIter.next();
				sb.append(index);
				int[] range = axisRangeMap.get(index);
				for (int i = 0; i < range.length; i++) {
					sb.append(",").append(range[i]);
				}
				if (indexIter.hasNext())
					sb.append("|"); // index split
			} // end: while (indexIter.hasNext())
			queries.add(sb.toString());
			sb.setLength(0);
		} // end: if (axisRangeMap.size() > 0)
	}

	private void bindAxisStyleQuery(List<String> queries) {
		if (axisStyleMap.size() > 0) {
			StringBuilder sb = new StringBuilder();
			sb.append(AXIS_STYLE_PARAM);
			Iterator<Integer> indexIter = axisStyleMap.keySet().iterator();
			while (indexIter.hasNext()) {
				Integer index = indexIter.next();
				AxisStyle style = axisStyleMap.get(index);
				sb.append(index).append(",").append(style.color);
				if (style.fontSize != null) {
					sb.append(",").append(style.fontSize);
				}
				if (style.alignment != null) {
					sb.append(",").append(style.alignment);
				}
				if (indexIter.hasNext())
					sb.append("|"); // index split
			} // end: while (indexIter.hasNext())
			queries.add(sb.toString());
			sb.setLength(0);
		} // end: if (axisRangeMap.size() > 0)
	}

	public String[] getQuery() {
		List<String> queries = new ArrayList<String>();
		bindAxisTypeQuery(queries);
		bindAxisLabelPositionsQuery(queries);
		bindAxisRangeQuery(queries);
		bindAxisLabelsQuery(queries);
		bindAxisStyleQuery(queries);

		if (queries.size() > 0)
			return queries.toArray(new String[queries.size()]);

		return new String[] { "" };
	}

	private QueryEncoderException genQueryEncoderException(String data,
			String codec, Throwable cause) {
		return new QueryEncoderException("axis(" + data + ") encode(" + codec
				+ ") error.", cause);
	}

	public static class AxisStyle {

		String color;

		Integer fontSize;

		Integer alignment;

		public AxisStyle(String color) {
			this.color = color;
		}

		public AxisStyle(String color, int fontSize) {
			this.color = color;
			setFontSize(fontSize);
		}

		public AxisStyle setFontSize(int fontSize) {
			this.fontSize = fontSize;
			return this;
		}

		public AxisStyle setAlignmentCenter() {
			alignment = 0;
			return this;
		}

		public AxisStyle setAlignmentLeft() {
			alignment = -1;
			return this;
		}

		public AxisStyle setAlignmentRight() {
			alignment = 1;
			return this;
		}
	}

}
